/*
 *	$Id: plotter.h 122 2009-06-11 09:31:45Z wabsie $
 */

#ifndef _PLOTTER_H
#define	_PLOTTER_H

#include <agar/core.h>	// Agar
#include <agar/gui.h>	// ...

#include <cmath>		// STL udgave af math.h (stort set ens)
#include <vector>		// STL vector
#include <algorithm>	// STL algorithm, sort
#include <queue>		// STL queue, priority_queue
#include <limits>		// STL limits, infinity
#include <utility>		// STL utility
#include <map>			// STL map
#include <list>			// STL set
#include "matematik.h"	// point2d, vektor og matrix
#include "emobj.h"		// emobjekter

#define LABEL(x, pot)	AG_TextRenderf("%.2fE%02.0f", (x)/pow(10, (pot)), (pot))
#define MIN(X, Y)		((X) < (Y) ? (X) : (Y))

using namespace std;	// STL ligger namespace 'std'


struct interval {
	skalar min;
	skalar max;
};

struct viewbox3d {
	interval x, y, z;			// Akseintervaller
	skalar alpha, beta, gamma;	// Euler vinkler
};

struct view {
	bool	adaptiveColor;
    int		plotWidth;
    int		plotHeigth;
    int		intensityWidth;
    int		intensityHeigth;
	char	cslice;			// z'et i "z = 0"
	float	nslice;			// nullet i "z = 0"
    float   firstAxMin;     // Første aksens min værdi
    float   firstAxMax;     // Første aksens max værdi
    float   secondAxMin;	// Anden aksens min værdi
    float   secondAxMax;	// Anden aksens max værdi
	viewbox3d view3d;		// Bruges kun til 3D
};

static const Uint32 colorRed = 0x0000FF;
static const Uint32 colorGreen = 0x00FF00;
static const Uint32 colorBlue = 0xFF0000;
static const Uint32 colorBlack = 0x000000;
static const Uint32 colorAlmostBlack = 0x101010;
static const Uint32 colorGrey = 0x4C4C4C;
static const Uint32 colorBordeaux = 0x00008C;
static const Uint32 colorWhite = colorRed | colorGreen | colorBlue;
static const Uint32 colorDefault = colorAlmostBlack;

struct linie {
	typedef pair<punkt*,punkt*> punkter;
	punkter par;
	Uint32 farve;
};

struct flade {
	vector<punkt> punkter;
};

struct polygon {
	typedef multimap<string,punkt> mapp;
	typedef mapp::iterator mappit;

	mapp punkter;
	vector<linie> linier;
	vector<flade> flader;

	void addPoint(string id, punkt p)
	{
		p.id = id;	// Override default id
		punkter.insert(pair<string,punkt>(id, p));
	}

	void makeFlade(plotter *p, string id0, string id1, string id2, string id3, int xyz, skalar val)
	{
		coord<3> c_orig[4];
		punkt ps[4];
		c_orig[0] = (punkter.find(id0)->second).orig;
		c_orig[1] = (punkter.find(id1)->second).orig;
		c_orig[2] = (punkter.find(id2)->second).orig;
		c_orig[3] = (punkter.find(id3)->second).orig;

		flade ny;
		for (int i = 0; i < 4; i++) {
			c_orig[i].data[xyz][0] = val;
			ny.punkter.push_back(c_orig[i].toPunkt(p));
		}

		this->flader.push_back(ny);
	}

	void makeLine(string id1, string id2, Uint32 col = colorWhite)
	{
		mappit p1 = punkter.find(id1);
		mappit p2 = punkter.find(id2);

		linie ny;
		ny.par = linie::punkter(&(p1->second), &(p2->second));
		ny.farve = col;
		this->linier.push_back(ny);
	}
};

class plotter
{
public:
	plotter(view parameters);
	~plotter();
	void tegn2d(const Medium& vem);
	void tegn3d(const Medium& vem, skalar f, skalar W, coord<3> centrum);
	void msgNoSuitableObjects();
	void msgNoObjects();

	AG_Surface* getPlot();
    AG_Surface* getIntensity();

	interval farve;
	struct slices {
		skalar planes[3];			// Hvert sliceplan har sit index (x=0,y=1,z=2) som normal, og værdien angiver elevationen
		int current;				// Angivet indexet for planet (x=0,y=1,z=2)
	} slice;
	
	// Tillad adgang til private plotter members, bl.a. origo og offset
	friend pixel<2> coord<2>::toPixel(plotter *p) const;
	friend coord<2> pixel<2>::toCoord(plotter *p) const;
	friend punkt coord<3>::toPunkt(plotter *p) const;

private:
	AG_Surface *plot;	// Plot surface hvor felterne vises
    AG_Surface *skala;	// Farveskalaen over feltintensiteten
	matrix<> *zbuffer;	// Z-buffer

	skalar f, W;
	coord<3> centrum;
	skalar alpha;
	skalar beta;
	skalar gamma;

	const view param;
	const int w, h;
	const skalar xmin, xmax, ymin, ymax, xrange, yrange;
	const pixel<2> centre, scale, offset, origo;
	static AG_PixelFormat *format;
	static const int minimumPixels = 10;// Mindste surface størrelse: 10x10 pixels
	static const skalar headLen = 6;	// Længde af pilens hoved i pixels
	static const skalar headWid = 3;	// Halv bredde af pilens hoved i pixels
	static const skalar pilLen = 15;	// Pil-længden i pixels
	static const int distPile = 22;		// Afstand i pixels mellem pile-start
	static const int tickLen = 2;		// Halv højde el. bredde af tickmarks

	inline void adaptivFarve(const matrix<>& m);
	inline void adaptivFarve3D(map< int,map<int,skalar> > *intensities);

	inline void calcIntensity(const Medium& vem, matrix<>& m, Field::pripot felt);
	inline void calcPlaneZ(const Medium& vem, const vector<punkt>& punkter,
		map< int,map<int,skalar> >& intensities);

	inline void plotBackground(const matrix<>& m);
	inline void plotBackground3D(map< int,map<int,skalar> > *intensities);

	inline void plotVectorField(const Medium& vem);
	inline void plotEquipotentialLines(const Medium& vem);

	inline void drawLine(const pixel<2>& p1, const pixel<2>& p2, Uint32 col = colorDefault);
	inline void drawLine(int x1, int y1, int x2, int y2, Uint32 col = colorDefault);
	inline void drawLine(AG_Surface *surf, int x1, int y1, int x2, int y2, Uint32 col = colorDefault);

	inline void drawLineZ(const punkt& p1, const punkt& p2, Uint32 col);
	inline void pseudoLine(vector< list<int> >& ytable, const pixel<2>& p1, const pixel<2>& p2);

	inline bool drawArrow(const pixel<2>& p1, const pixel<2>& p2, Uint32 col = colorDefault);
	inline void drawAxis(char axis1, char axis2);
	inline bool drawNormedVector(const coord<2>& pstart, const vektor<2>& vek, Uint32 col = colorBordeaux);
	inline void drawNormedVectorZ(	const punkt& start, const vektor<3>& vek,
									skalar vNorm, Uint32 col);
	inline void drawInOutVector(int x, int y, bool out, const Uint32 c = colorBordeaux);

	inline Uint32 GetColour(skalar v, skalar vmin, skalar vmax);
	inline Uint32 hsv(skalar h, skalar s, skalar v);
};


inline coord<2> pixel<2>::toCoord(plotter *p) const
{
	return (*this - p->origo).div(p->scale);
}

inline pixel<2> coord<2>::toPixel(plotter *p) const
{
	return p->origo + p->scale.mul(*this);
}

inline punkt coord<3>::toPunkt(plotter *p) const
{	
	punkt tmp;
	tmp.id = this->toString();
	tmp.orig = *this;

	// Offset
	tmp.offrot = tmp.orig - p->centrum;

	/*
	 * Rotér
	 *
	 * Kilde:
	 * http://en.wikipedia.org/w/index.php?title=Euler_angles&oldid=295327367 ,
	 * Afsnit: 'Euler angles as composition of extrinsic rotations'
	 */
	tmp.offrot = tmp.offrot.rotz(p->gamma);	// NB: roty behøves ikke
	tmp.offrot = tmp.offrot.rotx(p->beta);		// idet vi benytter
	tmp.offrot = tmp.offrot.rotz(p->alpha);	// z-x-z konventionen.

	// Projicér 3D punkt til 3D punkt (ja, _til_ til 3D punkt)
	skalar proj = (p->f - p->W)/(p->f - tmp.offrot.z);
	tmp.proj.x = tmp.offrot.x * proj;
	tmp.proj.y = tmp.offrot.y * proj;
	tmp.proj.z = tmp.offrot.z / (p->f - tmp.offrot.z); // Behold linearitet

	// Find tilsvarende pixel coords
	tmp.pixl = coord<2>(tmp.proj.x, tmp.proj.y).toPixel(p);
	
	return tmp;
}

#endif	/* _PLOTTER_H */

